﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Gallio.Model;
using Gallio.Model.Execution;
using Gallio.Runtime.ProgressMonitoring;
using NFit.Core.Interfaces;
using NFit.Core;

namespace NFit.GallioAdapter
{
    /// <summary>
    /// Controls the execution of NFit tests     
    /// </summary>
    public class NFitTestController : BaseTestController
    {
        private IExecutionContext context;

        public NFitTestController(ISpecificationDataSource source)
        {
            context = NFit.Core.ServiceLocator.Resolve<IExecutionContext>();
        }

        protected override TestOutcome RunTestsImpl(ITestCommand rootTestCommand, ITestStep parentTestStep, TestExecutionOptions options, IProgressMonitor progressMonitor)
        {
            SimpleLog.Write("Running test " + rootTestCommand.Test.Name);
            using (progressMonitor.BeginTask("Starting NFit tests run", rootTestCommand.TestCount))
            {
                if (options.SkipTestExecution)
                {
                    SkipAll(rootTestCommand, parentTestStep);
                    return TestOutcome.Skipped;
                }
                else
                {
                    bool success = RunTest(rootTestCommand, parentTestStep, progressMonitor);
                    return success ? TestOutcome.Passed : TestOutcome.Failed;
                }
            }
        }

        private bool RunTest(ITestCommand testCommand, ITestStep parentTestStep, IProgressMonitor progressMonitor)
        {
            bool passed;
            try
            {
                ITest test = testCommand.Test;
                progressMonitor.SetStatus(test.Name);
                SimpleLog.Write("Running " + test.Name);
                NFitTest nfitTest = test as NFitTest;
                if (nfitTest == null || nfitTest.Spec == null)
                {
                    passed = RunChildTests(testCommand, parentTestStep, progressMonitor);
                }
                else
                {
                    passed = RunSpec(testCommand, nfitTest.Spec, parentTestStep);
                }

                progressMonitor.Worked(1);
            }
            catch (Exception ex)
            {
                SimpleLog.Write(ex.ToString());
                throw;
            }
            return passed;
        }

        private bool RunChildTests(ITestCommand testCommand, ITestStep parentTestStep, IProgressMonitor progressMonitor)
        {
            ITestContext testContext = testCommand.StartPrimaryChildStep(parentTestStep);

            bool passed = true;
            foreach (ITestCommand child in testCommand.Children)
                passed &= RunTest(child, testContext.TestStep, progressMonitor);

            testContext.FinishStep(passed ? TestOutcome.Passed : TestOutcome.Failed, null);
            return passed;
        }

        private bool RunSpec(ITestCommand testCommand, SpecificationDefinition spec,
            ITestStep parentTestStep)
        {
            ITestContext testContext = testCommand.StartPrimaryChildStep(parentTestStep);
            bool passed = false;
            try
            {                
                DefaultSpecificationExecution runner = new DefaultSpecificationExecution();
                runner.Run(context, spec);
                passed = spec.IsSuccessful;
                
                SimpleLog.Write("Test " + spec.Name + " is successful " + passed);
            }
            catch (Exception ex)
            {
                SimpleLog.Write(ex.ToString());
            }
            LogTestResult(testContext, spec);
            testContext.FinishStep(passed ? TestOutcome.Passed : TestOutcome.Failed, null);
            return passed;
        }

        private void LogTestResult(ITestContext specContext, SpecificationDefinition spec)
        {
            if (spec.Status.IsSuccessful)
            {
                specContext.LogWriter.Default.Write("Specification succeeded " + spec.Name);
            }
            else
            {
                specContext.LogWriter.Default.Write("Specification failed " + spec.Name);
            }
            
            foreach(var fixture in spec.Fixtures)
            {
                SimpleLog.Write("Writing fixture");
                FixtureTestStep fixt = new FixtureTestStep(specContext.TestStep.Test, specContext.TestStep, fixture);
                ITestContext fixtureContext = specContext.StartChildStep(fixt);
                
                fixtureContext.FinishStep(GetOutcome(fixture.Status),null);
            }
        }

        private TestOutcome GetOutcome(Status status)
        {
            TestOutcome result = TestOutcome.Failed;
            if (status.IsSuccessful)
                result = TestOutcome.Passed;
            return result;
        }
    }
}
